/*
 * Copyright (C) 2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;

using DCSharp.Backend.Connections;
using DCSharp.Backend.Objects;
using DCSharp.Hashing;
using DCSharp.Logging;
using DCSharp.Settings;

namespace DCSharp.Backend.Managers
{
	/// <summary>
	/// The state of a <see cref="Downloader"/>.
	/// </summary>
	public enum DownloadState
	{
		Stopped,
		Loading,
		Idle,
		Active,
		Done
	}

	public class DownloadEventArgs : EventArgs
	{
		private Downloader downloader;

		public DownloadEventArgs(Downloader downloader)
		{
			this.downloader = downloader;
		}

		public Downloader Downloader
		{
			get { return downloader; }
		}
	}

	public class ConnectionRequestEventArgs : EventArgs
	{
		private User user;
		private RequestCallback callback;
		private bool handled;

		public ConnectionRequestEventArgs(User user, RequestCallback callback)
		{
			this.user = user;
			this.callback = callback;
		}

		public User User
		{
			get { return user; }
		}

		public RequestCallback Callback
		{
			get { return callback; }
		}

		public bool Handled
		{
			get { return handled; }
			set { handled = value; }
		}
	}

	public delegate void RequestCallback(User user, bool success);

	/// <summary>
	/// Base class for all the <see cref="Downloader"/> implementations.
	/// </summary>
	public abstract class Downloader : IDisposable
	{
		private static Logger log = LogManager.GetLogger("Downloader");

		public event EventHandler StateChanged;
		public event EventHandler<ConnectionRequestEventArgs> ConnectionRequest;

		internal event EventHandler<UserEventArgs> UserInteresting;
		internal event EventHandler<UserEventArgs> UserUninteresting;

		private DownloadState state;
		private object stateLock;
		private double progress;
		private Exception exception;

		private ConnectionMonitor monitor;
		private int lastUpdate;

		/// <summary>
		/// Initializes a new <see cref="Downloader"/> instance.
		/// </summary>
		protected Downloader()
		{
			monitor = new ConnectionMonitor();

			state = DownloadState.Stopped;
			stateLock = new Object();
		}

		~Downloader()
		{
			Dispose(false);
		}

		#region Properties

		/// <value>
		/// The state of the <see cref="Downloader"/>.
		/// </value>
		public DownloadState State
		{
			get { return state; }
			protected set
			{
				if (value != state)
				{
					state = value;
					OnStateChanged();
				}
			}
		}

		/// <summary>
		/// Gets the exception causing the <see cref="Downloader"/> to stop.
		/// </summary>
		public Exception Exception
		{
			get { return exception; }
			protected set { exception = value; }
		}

		/// <value>
		/// The size of what's being downloaded.
		/// </value>
		public abstract long FileSize
		{
			get;
		}

		/// <value>
		/// The download progress.
		/// </value>
		public double Progress
		{
			get { return progress; }
			protected set { progress = value; }
		}

		/// <value>
		/// 
		/// </value>
		public ConnectionMonitor Monitor
		{
			get { return monitor; }
		}

		/// <value>
		/// 
		/// </value>
		public abstract object UserLock
		{
			get;
		}

		/// <summary>
		/// Gets the users the downloader is interested in.
		/// </summary>
		internal abstract IEnumerable<User> InterestingUsers
		{
			get;
		}

		/// <value>
		/// 
		/// </value>
		protected object StateLock
		{
			get { return stateLock; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Starts the <see cref="Downloader"/>.
		/// </summary>
		public virtual void Start()
		{
			lock (stateLock)
			{
				if (State == DownloadState.Stopped)
				{
					log.Info("Starting", null, this);
					State = DownloadState.Idle;

					RequestConnections();
				}
			}
		}

		/// <summary>
		/// Stops the <see cref="Downloader"/>.
		/// </summary>
		public virtual void Stop()
		{
			lock (stateLock)
			{
				if (State != DownloadState.Done && State != DownloadState.Stopped)
				{
					log.Info("Stopping", null, this);

					exception = null;
					State = DownloadState.Stopped;
				}
			}
		}

		/// <summary>
		/// The remaining time before the <see cref="Downloader"/> is finished.
		/// Only available when the <see cref="Downloader"/> is active.
		/// </summary>
		public TimeSpan GetRemainingTime()
		{
			long remainingBytes = (long)((1 - progress) * FileSize);
			int remainingSeconds = 0;

			double bps = monitor.DownloadSpeed;
			if (bps > 0)
			{
				remainingSeconds = (int)(remainingBytes / bps);
			}
			return TimeSpan.FromSeconds(remainingSeconds);
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Tells the <see cref="Downloader"/> to make connection requests to the
		/// users it wants to download from.
		/// </summary>
		protected abstract void RequestConnections();

		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				Stop();
			}
		}

		/// <summary>
		/// Updates the <see cref="ConnectionMonitor"/> with the recevied data.
		/// </summary>
		protected void UpdateMonitor(int received)
		{
			monitor.BytesReceived(received);

			uint elapsed = (uint)(Environment.TickCount - lastUpdate);
			if (elapsed >= 1000)
			{
				monitor.TimePeriodPassed();
				lastUpdate = Environment.TickCount;
			}
		}

		/// <summary>
		/// Sets the <see cref="Exception"/> property and stops the downloader.
		/// </summary>
		/// <param name="e">The <see cref="Exception"/> to set.</param>
		protected void StopWithException(Exception e)
		{
			lock (StateLock)
			{
				Exception = e;
				State = DownloadState.Stopped;
			}
		}

		protected virtual void OnStateChanged()
		{
			if (StateChanged != null)
			{
				StateChanged(this, EventArgs.Empty);
			}
		}

		protected virtual bool OnConnectionRequest(User user,
			RequestCallback callback)
		{
			ConnectionRequestEventArgs args = new ConnectionRequestEventArgs(
				user, callback);
			if (ConnectionRequest != null)
			{
				ConnectionRequest(this, args);
			}
			return args.Handled;
		}

		protected virtual void OnUserInteresting(User user)
		{
			if (UserInteresting != null)
			{
				UserInteresting(this, new UserEventArgs(user));
			}
		}

		protected virtual void OnUserUninteresting(User user)
		{
			if (UserUninteresting != null)
			{
				UserUninteresting(this, new UserEventArgs(user));
			}
		}

		internal abstract void HandleIdleUser(User user);
		internal abstract bool Use(UserConnection connection);

		#endregion
	}
}
